package cn.net.yugu.doraemon.event.spring;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import cn.net.yugu.doraemon.api.common.event.EventHandler;
import cn.net.yugu.doraemon.common.arms.ArmsTraceExecutors;
import cn.net.yugu.doraemon.event.EventPostComponent;
import cn.net.yugu.doraemon.event.engine.EventHandlerRegistry;
import cn.net.yugu.doraemon.event.engine.GuavaEventBusComponent;
import cn.net.yugu.doraemon.event.handler.DefaultEventHandler;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * @author xiangzr
 * @version 2021/5/19 16:25:43
 */
@ConditionalOnClass(value = {EventBus.class, AsyncEventBus.class})
@AutoConfiguration
@Slf4j
public class EventBusAutoConfiguration {

    @ConfigurationProperties(prefix = "doraemon.event")
    @ConditionalOnMissingBean(value = EventBusProperties.class, name = "eventBusProperties")
    @Bean
    public EventBusProperties eventBusProperties() {
        return new EventBusProperties();
    }

    @ConditionalOnMissingBean(value = EventPostComponent.class, name = "guavaEventBusComponent")
    @Bean
    public EventPostComponent guavaEventBusComponent(EventBus eventBus,
                                                     EventBus asyncEventBus) {
        return new GuavaEventBusComponent(eventBus, asyncEventBus);
    }

    @ConditionalOnMissingBean(value = EventBus.class, name = "asyncEventBus")
    @Bean
    public EventBus asyncEventBus(Executor asyncEventBusThreadPoolExecutor) {
        AsyncEventBus asyncEventBus = new AsyncEventBus(asyncEventBusThreadPoolExecutor, getSubscriberExceptionHandler());
        return asyncEventBus;
    }

    @ConditionalOnMissingBean(value = EventBus.class, name = "eventBus")
    @Bean
    public EventBus eventBus() {
        EventBus eventBus = new EventBus(getSubscriberExceptionHandler());
        return eventBus;
    }

    @ConditionalOnMissingBean
    @Bean
    public DefaultEventHandler defaultEventHandler() {
        return new DefaultEventHandler();
    }

    @ConditionalOnMissingBean(value = EventHandlerRegistry.class)
    @Bean(initMethod = "init")
    public EventHandlerRegistry eventHandlerRegistry(List<EventHandler> eventHandlers,
                                                     EventBus eventBus,
                                                     EventBus asyncEventBus) {
        return new EventHandlerRegistry(eventHandlers, eventBus, asyncEventBus);
    }

    @ConditionalOnMissingBean(name = "asyncEventBusThreadPoolExecutor")
    @Bean
    public Executor asyncEventBusThreadPoolExecutor(EventBusProperties eventBusProperties) {
        int corePoolSize = 2;
        int maximumPoolSize = corePoolSize * 4;
        int capacity = 1 << 13;
        String name = "AsyncEventBus-Executor";

        final EventBusProperties.AsyncEventBusProperties asyncEventBusProperties = eventBusProperties.getAsyncEventBus();

        if (asyncEventBusProperties != null) {
            if (asyncEventBusProperties.getCorePoolSize() != null) {
                corePoolSize = asyncEventBusProperties.getCorePoolSize();
            }
            if (asyncEventBusProperties.getMaximumPoolSize() != null) {
                maximumPoolSize = asyncEventBusProperties.getMaximumPoolSize();
            }
            if (asyncEventBusProperties.getQueueCapacity() != null) {
                capacity = asyncEventBusProperties.getQueueCapacity();
            }
            if (asyncEventBusProperties.getThreadName() != null) {
                name = asyncEventBusProperties.getThreadName();
            }
        }
        final Boolean armsEnabled = Optional.ofNullable(asyncEventBusProperties)
                .map(EventBusProperties.AsyncEventBusProperties::getArmsEnabled)
                .orElse(false);
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(corePoolSize);
        taskExecutor.setMaxPoolSize(maximumPoolSize);
        taskExecutor.setQueueCapacity(capacity);
        taskExecutor.setKeepAliveSeconds(120);
        taskExecutor.setThreadNamePrefix(name);
        taskExecutor.setRejectedExecutionHandler((r, executor1) -> log.error("AsyncEventBus ThreadPoolExecutor is full !"));

        return armsEnabled ? ArmsTraceExecutors.wrapThreadPoolTaskExecutor(taskExecutor): taskExecutor;
    }

    private SubscriberExceptionHandler getSubscriberExceptionHandler() {
        return (exception, context) -> {
            final String subscriberMethodName = context.getSubscriberMethod().getName();
            final String subscriberType = context.getSubscriber().getClass().getCanonicalName();
            final String eventType = context.getEvent().getClass().getCanonicalName();
            log.error("EventBus handle Exception eventType {} , subscriberType {} subscriberMethodName {}",
                      eventType, subscriberType, subscriberMethodName, exception);
        };
    }
}
